/**
 * @file nac_helper.c
 * NDN gateway app's common functions
 * Chengyu Fan <chengyu@cs.colostate.edu>
 */

#include "nac_helper.h"


extern string
Concat (string s1, string s2) {
	if (s1 == NULL && s2 == NULL) return NULL;
	int len1 = ((s1 != NULL) ? strlen(s1) : 0);
	int len2 = ((s2 != NULL) ? strlen(s2) : 0);
	char *s = (char *) calloc(len1+len2+1, sizeof(char));
	int i = 0;
	for (i = 0; i < len1; i++) s[i] = s1[i];
	for (i = 0; i < len2; i++) s[len1+i] = s2[i];
	return s;
}

struct pheader *get_header(uint8_t type){
    //construct the packet header
    struct pheader *header = (struct pheader *)malloc(sizeof (struct pheader));
    header->ver = 64;//01000000, the first 2 bits are version
    header->msgType = type;
    header->length = 0;
    return header;
}

struct packet_act *get_packet_act(char *myip, int iport, int len, int protocol){
    uint16_t port = iport & 0xffff;
    uint8_t nameLen = len & 0xff;
    uint8_t prot = protocol & 0xff;

    //construct the packet content
    struct packet_act *act = (struct packet_act *)malloc(sizeof (struct packet_act));
    struct in_addr node_addr;
    int res = inet_aton(myip, &node_addr);
    if (res == 0){
        printf("inet_aton error\n");
        exit(1);
    }

    memcpy(act, &node_addr, sizeof (struct in_addr));
    act->port = htons(port);
    act->nameLen = nameLen;
    act->prot = prot;
    return act;
}

struct packet_ack *get_packet_ack(char *ccnip, int port, int protocol){
    uint16_t myport = port & 0xffff;
    uint8_t prot = protocol & 0xff;
    //construct the packet content
    struct packet_ack *ack = (struct packet_ack *)malloc(sizeof (struct packet_ack));
    struct in_addr node_addr;
    int res = inet_aton(ccnip, &node_addr);
    if (res == 0){
        printf("inet_aton error\n");
        exit(1);
    }

    memcpy(ack, &node_addr, sizeof (struct in_addr));

    ack->port = htons(myport);
    ack->end = 0;
    ack->prot = prot;
    return ack;
}

int is_valid_act(struct pheader *header){
    if((header->ver & 0x40) == 64 && header->msgType == MSGACT)
        return 0;
    return -1;
}

int is_valid_response(struct pheader *header){
    if((header->ver & 0x40) == 64 && (header->msgType == ACTACK || header->msgType == ACTNACK))
        return 0;
    return -1;
}

//////////////////////////////
void
nac_warn(int lineno, const char *format, ...)
{
    struct timeval t;
    va_list ap;
    va_start(ap, format);
    gettimeofday(&t, NULL);
    fprintf(stderr, "%d.%06d nac[%d]:%d: ", (int)t.tv_sec, (unsigned)t.tv_usec, (int)getpid(), lineno);
    vfprintf(stderr, format, ap);
    va_end(ap);
}

void
nac_fatal(int line, const char *format, ...)
{
    struct timeval t;
    va_list ap;
    va_start(ap, format);
    gettimeofday(&t, NULL);
    fprintf(stderr, "%d.%06d nac[%d]:%d: ", (int)t.tv_sec, (unsigned)t.tv_usec, (int)getpid(), line);
    vfprintf(stderr, format, ap);
    va_end(ap);
    exit(1);
}

#define ON_ERROR_EXIT(resval, msg) on_error_exit((resval), __LINE__, msg)

static void
on_error_exit(int res, int lineno, const char *msg)
{
    if (res >= 0)
        return;
    nac_fatal(lineno, "fatal error, res = %d, %s\n", res, msg);
}

#define ON_ERROR_CLEANUP(resval) \
{ 			\
if ((resval) < 0) { \
goto Cleanup; \
} \
}

#define ON_NULL_CLEANUP(resval) \
{ 			\
if ((resval) == NULL) { \
goto Cleanup; \
} \
}

void
initialize_global_data(struct ccn_charbuf *local_scope_template,
        struct ccn_charbuf *no_name) {
    const char *msg = "Unable to initialize global data.";
    /* Set up an Interest template to indicate scope 1 (Local) */
    if (local_scope_template == NULL) {
        ON_ERROR_EXIT(-1, msg);
    }
    
    ON_ERROR_EXIT(ccn_charbuf_append_tt(local_scope_template, CCN_DTAG_Interest, CCN_DTAG), msg);
    ON_ERROR_EXIT(ccn_charbuf_append_tt(local_scope_template, CCN_DTAG_Name, CCN_DTAG), msg);
    ON_ERROR_EXIT(ccn_charbuf_append_closer(local_scope_template), msg);	/* </Name> */
    ON_ERROR_EXIT(ccnb_tagged_putf(local_scope_template, CCN_DTAG_Scope, "1"), msg);
    ON_ERROR_EXIT(ccn_charbuf_append_closer(local_scope_template), msg);	/* </Interest> */
    
    /* Create a null name */
    if (no_name == NULL) {
        ON_ERROR_EXIT(-1, msg);
    }
    ON_ERROR_EXIT(ccn_name_init(no_name), msg);
}

/**
 *  @brief Register an interest prefix as being routed to a given face
 *  @param h  the ccnd handle
 *  @param name_prefix  the prefix to be registered
 *  @param face_instance  the face to which the interests with the prefix should be routed
 *  @param flags
 *  @result returns (positive) faceid on success, -1 on error
 */
static int
register_unregister_prefix(struct ccn *h,
                           int operation,
                           struct ccn_charbuf *name_prefix,
                           struct ccn_face_instance *face_instance,
                           int flags, int lifetime,
                           struct global_data *g_data)
{
    struct ccn_charbuf *temp = NULL;
    struct ccn_charbuf *resultbuf = NULL;
    struct ccn_charbuf *signed_info = NULL;
    struct ccn_charbuf *name = NULL;
    struct ccn_charbuf *prefixreg = NULL;
    struct ccn_parsed_ContentObject pcobuf = {0};
    struct ccn_forwarding_entry forwarding_entry_storage = {0};
    struct ccn_forwarding_entry *forwarding_entry = &forwarding_entry_storage;
    struct ccn_forwarding_entry *new_forwarding_entry;
    const unsigned char *ptr = NULL;
    size_t length = 0;
    int res;
    struct ccn_charbuf *no_name;
    struct ccn_charbuf *local_scope_template;
    if (g_data != NULL){
        no_name = g_data->no_name;
        local_scope_template = g_data->local_scope_template;
    }
    /* Register or unregister the prefix */
    forwarding_entry->action = (operation == OP_REG) ? "prefixreg" : "unreg";
    forwarding_entry->name_prefix = name_prefix;
    forwarding_entry->ccnd_id = face_instance->ccnd_id;
    forwarding_entry->ccnd_id_size = face_instance->ccnd_id_size;
    forwarding_entry->faceid = face_instance->faceid;
    forwarding_entry->flags = flags;
    //forwarding_entry->lifetime = (~0U) >> 1;
    forwarding_entry->lifetime = lifetime;
    
    prefixreg = ccn_charbuf_create();
    ON_NULL_CLEANUP(prefixreg);
    ON_ERROR_CLEANUP(ccnb_append_forwarding_entry(prefixreg, forwarding_entry));
    temp = ccn_charbuf_create();
    ON_NULL_CLEANUP(temp);
    res = ccn_sign_content(h, temp, no_name, NULL, prefixreg->buf, prefixreg->length);
    ON_ERROR_CLEANUP(res);    
    resultbuf = ccn_charbuf_create();
    ON_NULL_CLEANUP(resultbuf);
    name = ccn_charbuf_create();
    ON_ERROR_CLEANUP(ccn_name_init(name));
    ON_ERROR_CLEANUP(ccn_name_append_str(name, "ccnx"));
    ON_ERROR_CLEANUP(ccn_name_append(name, face_instance->ccnd_id, face_instance->ccnd_id_size));
    ON_ERROR_CLEANUP(ccn_name_append_str(name, (operation == OP_REG) ? "prefixreg" : "unreg"));
    ON_ERROR_CLEANUP(ccn_name_append(name, temp->buf, temp->length));
    res = ccn_get(h, name, local_scope_template, 1000, resultbuf, &pcobuf, NULL, 0);
    ON_ERROR_CLEANUP(res);
    ON_ERROR_CLEANUP(ccn_content_get_value(resultbuf->buf, resultbuf->length, &pcobuf, &ptr, &length));
    new_forwarding_entry = ccn_forwarding_entry_parse(ptr, length);
    ON_NULL_CLEANUP(new_forwarding_entry);
    
    res = new_forwarding_entry->faceid;

    ccn_forwarding_entry_destroy(&new_forwarding_entry);
    ccn_charbuf_destroy(&signed_info);
    ccn_charbuf_destroy(&temp);
    ccn_charbuf_destroy(&resultbuf);
    ccn_charbuf_destroy(&name);
    ccn_charbuf_destroy(&prefixreg);
    
    return (res);
    
    /* This is where ON_ERROR_CLEANUP sends us in case of an error
     * and we must free any storage we allocated before returning.
     */
Cleanup:
    ccn_charbuf_destroy(&signed_info);
    ccn_charbuf_destroy(&temp);
    ccn_charbuf_destroy(&resultbuf);
    ccn_charbuf_destroy(&name);
    ccn_charbuf_destroy(&prefixreg);
    
    return (-1);
}

/**
 *  @brief Create a face based on the face attributes
 *  @param h  the ccnd handle
 *  @param face_instance  the parameters of the face to be created
 *  @param flags
 *  @result returns new face_instance representing the face created
 */
static struct ccn_face_instance *
create_face(struct ccn *h, struct ccn_face_instance *face_instance, struct global_data *g_data)
{
    struct ccn_charbuf *newface = NULL;
    struct ccn_charbuf *signed_info = NULL;
    struct ccn_charbuf *temp = NULL;
    struct ccn_charbuf *name = NULL;
    struct ccn_charbuf *resultbuf = NULL;
    struct ccn_parsed_ContentObject pcobuf = {0};
    struct ccn_face_instance *new_face_instance = NULL;
    const unsigned char *ptr = NULL;
    size_t length = 0;
    int res = 0;
    struct ccn_charbuf *local_scope_template;
    struct ccn_charbuf *no_name;
    if (g_data != NULL){
        no_name = g_data->no_name;
        local_scope_template = g_data->local_scope_template;
    }
    /* Encode the given face instance */
    newface = ccn_charbuf_create();
    ON_NULL_CLEANUP(newface);
    ON_ERROR_CLEANUP(ccnb_append_face_instance(newface, face_instance));

    temp = ccn_charbuf_create();
    ON_NULL_CLEANUP(temp);
    res = ccn_sign_content(h, temp, no_name, NULL, newface->buf, newface->length);
    ON_ERROR_CLEANUP(res);
    resultbuf = ccn_charbuf_create();
    ON_NULL_CLEANUP(resultbuf);
    
    /* Construct the Interest name that will create the face */
    name = ccn_charbuf_create();
    ON_NULL_CLEANUP(name);
    ON_ERROR_CLEANUP(ccn_name_init(name));
    ON_ERROR_CLEANUP(ccn_name_append_str(name, "ccnx"));
    ON_ERROR_CLEANUP(ccn_name_append(name, face_instance->ccnd_id, face_instance->ccnd_id_size));
    ON_ERROR_CLEANUP(ccn_name_append_str(name, face_instance->action));
    ON_ERROR_CLEANUP(ccn_name_append(name, temp->buf, temp->length));
    res = ccn_get(h, name, local_scope_template, 1000, resultbuf, &pcobuf, NULL, 0);
    ON_ERROR_CLEANUP(res);
    
    ON_ERROR_CLEANUP(ccn_content_get_value(resultbuf->buf, resultbuf->length, &pcobuf, &ptr, &length));
    new_face_instance = ccn_face_instance_parse(ptr, length);
    ON_NULL_CLEANUP(new_face_instance);
    ccn_charbuf_destroy(&newface);
    ccn_charbuf_destroy(&signed_info);
    ccn_charbuf_destroy(&temp);
    ccn_charbuf_destroy(&resultbuf);
    ccn_charbuf_destroy(&name);
    return (new_face_instance);
    
Cleanup:
    ccn_charbuf_destroy(&newface);
    ccn_charbuf_destroy(&signed_info);
    ccn_charbuf_destroy(&temp);
    ccn_charbuf_destroy(&resultbuf);
    ccn_charbuf_destroy(&name);
    ccn_face_instance_destroy(&new_face_instance);
    return (NULL);
}


static struct prefix_face_list_item *prefix_face_list_item_create(struct ccn_charbuf *prefix,
                                                                  int ipproto,
                                                                  int mcast_ttl,
                                                                  char *host,
                                                                  char *port,
                                                                  char *mcastif,
                                                                  int lifetime,
                                                                  int flags)
{
    struct prefix_face_list_item *pfl = calloc(1, sizeof(struct prefix_face_list_item));
    struct ccn_face_instance *fi = calloc(1, sizeof(*fi));
    struct ccn_charbuf *store = ccn_charbuf_create();
    int host_off = -1;
    int port_off = -1;
    int mcast_off = -1;
    
    if (pfl == NULL || fi == NULL || store == NULL) {
        if (pfl) free(pfl);
        if (fi) ccn_face_instance_destroy(&fi);
        if (store) ccn_charbuf_destroy(&store);
        return (NULL);
    }
    pfl->fi = fi;
    pfl->fi->store = store;
    
    pfl->prefix = prefix;
    pfl->fi->descr.ipproto = ipproto;
    pfl->fi->descr.mcast_ttl = mcast_ttl;
    pfl->fi->lifetime = lifetime;
    pfl->flags = flags;
    
    ccn_charbuf_append(store, "newface", strlen("newface") + 1);
    host_off = store->length;
    ccn_charbuf_append(store, host, strlen(host) + 1);
    port_off = store->length;
    ccn_charbuf_append(store, port, strlen(port) + 1);
    if (mcastif != NULL) {
        mcast_off = store->length;
        ccn_charbuf_append(store, mcastif, strlen(mcastif) + 1);
    }
    // appending to a charbuf may move it, so we must wait until we have
    // finished appending before calculating the pointers into the store.
    char *b = (char *)store->buf;
    pfl->fi->action = b;
    pfl->fi->descr.address = b + host_off;
    pfl->fi->descr.port = b + port_off;
    pfl->fi->descr.source_address = (mcast_off == -1) ? NULL : b + mcast_off;
    
    return (pfl);
}


int
process_command_tokens(struct prefix_face_list_item *pfltail,
                       int lineno,
                       char *cmd,
                       char *uri,
                       char *proto,
                       char *host,
                       char *port,
                       char *flags,
                       char *mcastttl,
                       char *mcastif,
                       char *ribttl)
{
    int lifetime;
    struct ccn_charbuf *prefix;
    int ipproto;
    int socktype;
    int iflags;
    int imcastttl;
    char rhostnamebuf[NI_MAXHOST];
    char rhostportbuf[NI_MAXSERV];
    struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_flags = (AI_ADDRCONFIG)};
    struct addrinfo mcasthints = {.ai_family = AF_UNSPEC, .ai_flags = (AI_ADDRCONFIG | AI_NUMERICHOST)};
    struct addrinfo *raddrinfo = NULL;
    struct addrinfo *mcastifaddrinfo = NULL;
    struct prefix_face_list_item *pflp;
    int res;

    if (cmd == NULL) {
        nac_warn(__LINE__, "command error (line %d), missing command\n", lineno);
        return (-1);
    }   
    if (strcasecmp(cmd, "add") == 0){
        if (ribttl == NULL)
            lifetime = (~0U) >> 1;
        else
            lifetime = atoi(ribttl);
    }
    else if (strcasecmp(cmd, "del") == 0)
        lifetime = 0;
    else {
        nac_warn(__LINE__, "command error (line %d), unrecognized command '%s'\n", lineno, cmd);
        return (-1);
    }
    
    if (uri == NULL) {
        nac_warn(__LINE__, "command error (line %d), missing CCNx URI\n", lineno);
        return (-1);
    }   
    prefix = ccn_charbuf_create();
    res = ccn_name_from_uri(prefix, uri);
    if (res < 0) {
        nac_warn(__LINE__, "command error (line %d), bad CCNx URI '%s'\n", lineno, uri);
        return (-1);
    }
    
    if (proto == NULL) {
        nac_warn(__LINE__, "command error (line %d), missing address type\n", lineno);
        return (-1);
    }
    if (strcasecmp(proto, "udp") == 0) {
        ipproto = IPPROTO_UDP;
        socktype = SOCK_DGRAM;
    }
    else if (strcasecmp(proto, "tcp") == 0) {
        ipproto = IPPROTO_TCP;
        socktype = SOCK_STREAM;
    }
    else {
        nac_warn(__LINE__, "command error (line %d), unrecognized address type '%s'\n", lineno, proto);
        return (-1);
    }
    
    if (host == NULL) {
        nac_warn(__LINE__, "command error (line %d), missing hostname\n", lineno);
        return (-1);
    }
    
    if (port == NULL || port[0] == 0)
        port = CCN_DEFAULT_UNICAST_PORT;
    
    hints.ai_socktype = socktype;
    res = getaddrinfo(host, port, &hints, &raddrinfo);
    if (res != 0 || raddrinfo == NULL) {
        nac_warn(__LINE__, "command error (line %d), getaddrinfo: %s\n", lineno, gai_strerror(res));
        return (-1);
    }
    res = getnameinfo(raddrinfo->ai_addr, raddrinfo->ai_addrlen,
                      rhostnamebuf, sizeof(rhostnamebuf),
                      rhostportbuf, sizeof(rhostportbuf),
                      NI_NUMERICHOST | NI_NUMERICSERV);
    freeaddrinfo(raddrinfo);
    if (res != 0) {
        nac_warn(__LINE__, "command error (line %d), getnameinfo: %s\n", lineno, gai_strerror(res));
        return (-1);
    }
    
    iflags = -1;
    if (flags != NULL && flags[0] != 0) {
        iflags = atoi(flags);
        if ((iflags & ~CCN_FORW_PUBMASK) != 0) {
            nac_warn(__LINE__, "command error (line %d), invalid flags 0x%x\n", lineno, iflags);
            return (-1);
        }
    }
    
    imcastttl = -1;
    if (mcastttl != NULL) {
        imcastttl = atoi(mcastttl);
        if (imcastttl < 0 || imcastttl > 255) {
            nac_warn(__LINE__, "command error (line %d), invalid multicast ttl: %s\n", lineno, mcastttl);
            return (-1);
        }
    }
    
    if (mcastif != NULL) {
        res = getaddrinfo(mcastif, NULL, &mcasthints, &mcastifaddrinfo);
        if (res != 0) {
            nac_warn(__LINE__, "command error (line %d), mcastifaddr getaddrinfo: %s\n", lineno, gai_strerror(res));
            return (-1);
        }
    }
    
    /* we have successfully parsed a command line */
    pflp = prefix_face_list_item_create(prefix, ipproto, imcastttl, rhostnamebuf, rhostportbuf, mcastif, lifetime, iflags);
    if (pflp == NULL) {
        nac_fatal(__LINE__, "Unable to allocate prefix_face_list_item\n");
    }
    pfltail->next = pflp;
    return (0);
}

/*
 * this should eventually be used as the basis for a library function
 *    ccn_get_ccndid(...)
 * which would retrieve a copy of the ccndid from the
 * handle, where it should be cached.
 */
int
get_ccndid(struct ccn *h, const unsigned char *ccndid, size_t ccndid_storage_size, struct ccn_charbuf *local_scope_template)
{
    
    struct ccn_charbuf *name = NULL;
    struct ccn_charbuf *resultbuf = NULL;
    struct ccn_parsed_ContentObject pcobuf = {0};
    char ccndid_uri[] = "ccnx:/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY";
    const unsigned char *ccndid_result;
    static size_t ccndid_result_size;
    int res;
    
    name = ccn_charbuf_create();
    if (name == NULL) {
        ON_ERROR_EXIT(-1, "Unable to allocate storage for service locator name charbuf");
    }
    
    resultbuf = ccn_charbuf_create();
    if (resultbuf == NULL) {
        ON_ERROR_EXIT(-1, "Unable to allocate storage for result charbuf");
    }
    
    
    ON_ERROR_EXIT(ccn_name_from_uri(name, ccndid_uri), "Unable to parse service locator URI for ccnd key");
    ON_ERROR_EXIT(ccn_get(h, name, local_scope_template, 4500, resultbuf, &pcobuf, NULL, 0), "Unable to get key from ccnd");
    res = ccn_ref_tagged_BLOB(CCN_DTAG_PublisherPublicKeyDigest,
                              resultbuf->buf,
                              pcobuf.offset[CCN_PCO_B_PublisherPublicKeyDigest],
                              pcobuf.offset[CCN_PCO_E_PublisherPublicKeyDigest],
                              &ccndid_result, &ccndid_result_size);
    ON_ERROR_EXIT(res, "Unable to parse ccnd response for ccnd id");
    if (ccndid_result_size > ccndid_storage_size)
        ON_ERROR_EXIT(-1, "Incorrect size for ccnd id in response");
    memcpy((void *)ccndid, ccndid_result, ccndid_result_size);
    ccn_charbuf_destroy(&name);
    ccn_charbuf_destroy(&resultbuf);
    return (ccndid_result_size);
}

void
process_prefix_face_list_item(struct ccn *h,
                              struct prefix_face_list_item *pfl,
                              struct global_data *g_data) 
{
    struct ccn_face_instance *nfi;
    struct ccn_charbuf *temp;
    int op;
    int res;
    size_t ccndid_size;
    const unsigned char *ccndid;
    if (g_data != NULL){
        ccndid = g_data->ccndid;
        ccndid_size = g_data->ccndid_size;
    }

    op = (pfl->fi->lifetime > 0) ? OP_REG : OP_UNREG;
    pfl->fi->ccnd_id = ccndid;
    pfl->fi->ccnd_id_size = ccndid_size;
    nfi = create_face(h, pfl->fi, g_data);
    if (nfi == NULL) {
        temp = ccn_charbuf_create();
        ccn_uri_append(temp, pfl->prefix->buf, pfl->prefix->length, 1);
        nac_warn(__LINE__, "Unable to create face for %s %s %s %s %s\n",
                   (op == OP_REG) ? "add" : "del", ccn_charbuf_as_string(temp),
                   (pfl->fi->descr.ipproto == IPPROTO_UDP) ? "udp" : "tcp",
                   pfl->fi->descr.address,
                   pfl->fi->descr.port);
        ccn_charbuf_destroy(&temp);
        return;
    }
    res = register_unregister_prefix(h, op, pfl->prefix, nfi, pfl->flags, pfl->fi->lifetime, g_data);
    if (res < 0) {
        temp = ccn_charbuf_create();
        ccn_uri_append(temp, pfl->prefix->buf, pfl->prefix->length, 1);
        nac_warn(__LINE__, "Unable to %sregister prefix on face %d for %s %s %s %s %s\n",
                   (op == OP_UNREG) ? "un" : "", nfi->faceid,
                   (op == OP_REG) ? "add" : "del",
                   ccn_charbuf_as_string(temp),
                   (pfl->fi->descr.ipproto == IPPROTO_UDP) ? "udp" : "tcp",
                   pfl->fi->descr.address,
                   pfl->fi->descr.port);
        ccn_charbuf_destroy(&temp);
    }

    ccn_face_instance_destroy(&nfi);
    return;
}


void prefix_face_list_destroy(struct prefix_face_list_item **pflpp)
{
    struct prefix_face_list_item *pflp = *pflpp;
    struct prefix_face_list_item *next;
    
    if (pflp == NULL) return;
    while (pflp) {
        ccn_face_instance_destroy(&pflp->fi);
        ccn_charbuf_destroy(&pflp->prefix);
        next = pflp->next;
        free(pflp);
        pflp = next;
    }
    *pflpp = NULL;
}



